package com.nado.extensions.util;

import com.nado.extensions.util.DomainModel.COMMAND;
import com.nado.extensions.util.DomainModel.FIELD;

import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 미리 정의된 {@link DomainModel}을 통하여 Map 형태의 내부 데이터에 대한 정형화된 뷰를 제공하는 특수한 형태의 Map.
 * <p/>
 * 각 필드는 {@link DomainModel.FIELD}를 통하여 접근하도록 되어 있으며, 필드의 값은 하나 이상 존재할 수 있다.
 * 이 경우 해당 필드는 {@link List}와 유사한 API를 통해(인덱스를 통한 접근, null 값 허용 등) 사용할 수 있다.
 * <p/>
 * Generic Parameter를 통해 내부 Element의 Type을 지정할 수 있으며, Key Type은 항상 String으로 간주한다.
 *
 * @see DomainModel
 * @see StringDomainMap
 */
public interface DomainMap<T> extends Map<String, Object> {

	/**
	 * String 값을 encode/decode 하기 위한 캐릭터 세트을 지정한다.
	 *
	 * @param charset 캐릭터 세트의 이름
	 * @return 새로운 캐릭터 세트으로 변경되었는지 여부
	 */
	boolean setCharset(String charset);

	/**
	 * 지정되어 있는 캐릭터 세트을 반환한다.
	 *
	 * @return 캐릭터 세트의 이름
	 */
	String getCharset();

	/**
	 * DomainMap이 처리하고자 하는 COMMAND를 설정한다.
	 *
	 * @param value {@link DomainModel.COMMAND}
	 * @return chain execution을 위해 this를 반환한다.
	 */
	DomainMap<T> setCommand(COMMAND value);

	/**
	 * 설정된 COMMAND를 반환한다. 설정되지 않은 경우 null을 반환한다.
	 *
	 * @return 설정된 {@link DomainModel.COMMAND}
	 */
	COMMAND getCommand();

	/**
	 * Enum 타입의 COMMAND를 사용하는 경우 DomainModel을 이용하여 원래 Type을 얻는다. 설정되지 않은 경우 null을 반환한다.
	 *
	 * @param model COMMAND를 정의한 DomainModel 인스턴스
	 * @return COMMAND의 원래 Type
	 * @throws IllegalArgumentException 지정된 COMMAND가 DomainModel에 없는 경우
	 */
	<C extends Enum & DomainModel.COMMAND> C resolveCommand(DomainModel<?, C> model);

	/**
	 * DomainModel에 정의된 필드 이외의 필드가 존재하는 경우 Exception이 발생한다.
	 *
	 * @param model {@link DomainModel}
	 * @throws IllegalArgumentException 조건에 맞지 않는 경우
	 */
	void checkFields(DomainModel model);

	/**
	 * 지정된 COMMAND에 required()로 정의된 필드의 값이 null인 경우 Exception이 발생한다.
	 *
	 * @param model {@link DomainModel}
	 * @throws IllegalArgumentException 조건에 맞지 않는 경우
	 */
	void checkRequired(DomainModel model);

	/**
	 * 지정된 COMMAND에 required() 혹은 optional()로 정의된 필드의 이외의 값이 존재하는 경우 Exception이 발생한다.
	 *
	 * @param model {@link DomainModel}
	 * @throws IllegalArgumentException 조건에 맞지 않는 경우
	 */
	void checkExclusive(DomainModel model);

	/**
	 * checked behavior를 통해 generic으로 지정되지 않은 Type은 DomainMap 내에 존재하지 않도록 한다.
	 * 해당 클래스의 인스턴스가 아닌 값을 set/add/put/putAll을 통해 넣으려는 경우 ClassCastException이 발생한다.
	 *
	 * @param type generic 타입의 클래스
	 */
	void setTypeCheck(Class<T> type);

	/**
	 * 해당 필드에 할당된 값이 있는지 조사한다.
	 *
	 * @param key FIELD
	 * @return 값 할당 여부
	 */
	boolean containsField(FIELD key);

	/**
	 * 해당 필드의 값을 세팅한다.
	 */
	DomainMap<T> setField(FIELD key, T value);

	/**
	 * 해당 필드의 값을 세팅한다.
	 */
	DomainMap<T> setField(FIELD key, T... values);

	/**
	 * 해당 필드의 값을 세팅한다.
	 */
	DomainMap<T> setField(FIELD key, List<T> values);

	/**
	 * 해당 필드에 값을 추가한다.
	 */
	DomainMap<T> addField(FIELD key, T value);

	/**
	 * 해당 필드에 값을 추가한다.
	 */
	DomainMap<T> addField(FIELD key, T... values);

	/**
	 * 해당 필드에 값을 추가한다.
	 */
	DomainMap<T> addField(FIELD key, List<T> values);

	/**
	 * {@link DomainModel}에 정의된 모든 필드의 값들을 제거한다.
	 * DomainModel에 정의되지 않은 필드는 그대로 존재한다.
	 */
	DomainMap<T> clearFields(DomainModel model);

	/**
	 * 모든 필드를 삭제한다. 기능상으로 {@link #clear}와 완전히 동일하다.
	 */
	DomainMap<T> clearAll();

	/**
	 * 해당 필드의 첫번째 값을 가져온다. 존재하지 않는 경우 null을 반환한다.
	 */
	T getField(FIELD key);

	/**
	 * 해당 필드의 첫번째 값을 가져온다. 존재하지 않는 경우 defaultValue를 반환한다.
	 */
	T getField(FIELD key, T defaultValue);

	/**
	 * 해당 필드의 n번째 값을 가져온다. 존재하지 않는 경우 null을 반환한다.
	 */
	T getField(FIELD key, int nth);

	/**
	 * 해당 필드의 n번째 값을 가져온다. 존재하지 않는 경우 defaultValue를 반환한다.
	 */
	T getField(FIELD key, int nth, T defaultValue);

	/**
	 * 해당 필드의 모든 값을 리스트 형태로 가져온다. 존재하지 않는 경우 empty list를 반환한다.
	 */
	List<T> getFieldList(FIELD key);

	/**
	 * 해당 필드의 첫번째 값을 가져온다. 해당 값은 Map에서 삭제되고 이후의 값들은 인덱스가 하나씩 감소하게 된다.
	 * 존재하지 않는 경우 null을 반환한다.
	 */
	T removeField(FIELD key);

	/**
	 * 해당 필드의 첫번째 값을 가져온다. 해당 값은 Map에서 삭제되고 이후의 값들은 인덱스가 하나씩 감소하게 된다.
	 * 존재하지 않는 경우 defaultValue를 반환한다.
	 */
	T removeField(FIELD key, T defaultValue);

	/**
	 * 해당 필드의 n번째 값을 가져온다. 해당 값은 Map에서 삭제되고 이후의 값들은 인덱스가 하나씩 감소하게 된다.
	 * 존재하지 않는 경우 null을 반환한다.
	 */
	T removeField(FIELD key, int nth);

	/**
	 * 해당 필드의 n번째 값을 가져온다. 해당 값은 Map에서 삭제되고 이후의 값들은 인덱스가 하나씩 감소하게 된다.
	 * 존재하지 않는 경우 defaultValue를 반환한다.
	 */
	T removeField(FIELD key, int nth, T defaultValue);

	/**
	 * 해당 필드의 모든 값을 리스트 형태로 가져온다. 필드의 모든 값은 삭제된다.
	 * 존재하지 않는 경우 empty list를 반환한다.
	 */
	List<T> removeFieldList(FIELD key);

	/**
	 * 해당 필드에 있는 값의 개수를 반환한다.
	 */
	int getFieldCount(FIELD key);

	/**
	 * 해당 필드에 대하여 value 값의 인덱스를 반환한다. 존재하지 않는 경우 -1을 반환한다.
	 */
	int indexOf(FIELD key, Object value);

	/**
	 * 현재의 DomainMap에 대한 shallow copy를 생성한다.
	 * 하나의 필드에 다수의 값이 존재하는 경우 그 값들을 backing하는 List 객체는 새로 생성된다.
	 */
	DomainMap<T> duplicate();

	/**
	 * 현재의 DomainMap을 HashMap으로 변환한다.
	 * 하나의 필드에 다수의 값이 존재하는 경우 첫번째 값을 사용한다.
	 */
	Map<String, T> convert();

	/**
	 * 현재의 DomainMap을 HashMap으로 변환한다.
	 * 하나의 필드에 다수의 값이 존재하는 경우 인수로 넘긴 {@link ObjectAppender}를 이용하여 하나로 만들어서 넣는다.
	 *
	 * @param appender {@link ObjectAppender}
	 */
	Map<String, T> convert(ObjectAppender<T> appender);

	/**
	 * 현재의 DomainMap을 사용자가 제공한 Map으로 변환한다.
	 * 하나의 필드에 다수의 값이 존재하는 경우 첫번째 값을 사용한다.
	 *
	 * @param object 사용자가 생성한 Map 객체
	 * @return 파라미터 인스턴스(for chain execution)
	 */
	Map<String, T> convert(Map<String, T> object);

	/**
	 * 현재의 DomainMap을 사용자가 제공한 Map으로 변환한다.
	 * 하나의 필드에 다수의 값이 존재하는 경우 인수로 넘긴 {@link ObjectAppender}를 이용하여 하나로 만들어서 넣는다.
	 *
	 * @param object   사용자가 생성한 Map 객체
	 * @param appender {@link ObjectAppender}
	 * @return 파라미터 인스턴스(for chain execution)
	 */
	Map<String, T> convert(Map<String, T> object, ObjectAppender<T> appender);

	/**
	 * 필드 키에 대한 Iterator를 생성한다.
	 * DomainModel에 정의되지 않은 경우 next() 호출 시에 IllegalArgumentException이 발생한다.
	 */
	Iterator<FIELD> keyIterator(DomainModel model);

	/**
	 * 필드 엔트리에 대한 Iterator를 생성한다. 값이 두 개 이상 존재하는 필드의 경우 각 값에 대해 순서대로 엔트리를 전달한다.
	 * DomainModel에 정의되지 않은 경우 next() 호출 시에 IllegalArgumentException이 발생한다.
	 */
	Iterator<Entry<FIELD, T>> entryIterator(DomainModel model);

	/**
	 * 현재의 DomainMap을 ByteBuffer 형태로 변환한다.
	 * Serializer 가 지정되지 않은 경우 UnsupportedOperationException 이 발생한다.
	 *
	 * @throws UnsupportedOperationException
	 * @see #setSerializer(Serializer)
	 */
	ByteBuffer serialize(DomainModel model);

	/**
	 * ByteBuffer에서 현재 DomainMap 의 값을 읽어온다.
	 * Serializer 가 지정되지 않은 경우 UnsupportedOperationException 이 발생한다.
	 *
	 * @throws UnsupportedOperationException
	 * @see #setSerializer(Serializer)
	 */
	void deserialize(DomainModel model, ByteBuffer input);

	/**
	 * DomainMap 구현체를 직렬화 하기 위한 Serializer를 설정한다.
	 *
	 * @param serializer	{@link Serializer}을 implements한 직렬화 객체
	 */
	void setSerializer(Serializer serializer);

	/**
	 * DomainMap 을 직렬화 할때 사용되는 Hepler interface
	 */
	interface Serializer {
		ByteBuffer serialize(DomainModel model);
		void deserialize(DomainModel model, ByteBuffer input);
	}
}
